/***************************************************************************
 *
 *   copyright            : (C) 2005 Winds of Storm
 *
 *   $Id: events.c,v 1.26 2007/06/03 05:58:42 nonny Exp $
 *
 ***************************************************************************/

/***************************************************************************
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 ***************************************************************************/

#include "sb.h"

int update_session(DESCRIPTOR_DATA *desc) {
        DB_RES  *results = NULL;
        DB_ROW  row = NULL;
	char	buffer[MAX_STRING_LENGTH];
	//int	num_rows	= 0;

	if (desc == NULL) {
		sb_log(LOG_CRIT, "update_session: descriptor is NULL!");
		return -1;
	}

	if ((char *)desc->session_id == NULL) {
		sb_log(LOG_CRIT, "update_session: descriptor session ID is NULL!");
		sb_log(LOG_CRIT, "update_session: descriptor id is %d", desc->descriptor);
		return -1;
	}

	if (desc->conn_status == CON_QUITTING) {
		sb_log(LOG_INFO, "update_session: descriptor %d is logging out...", desc->descriptor);
	}

	sprintf(buffer, "SELECT room_id FROM %ssessions WHERE session_id='%s'", db_pfx, (char *)desc->session_id);
	pthread_mutex_lock(&db_mutex);
        results	= db_query(dbconn, buffer);
	pthread_mutex_unlock(&db_mutex);

        if (results != NULL) {
		//num_rows	= db_num_rows(results);
		row		= db_fetch_row(results, 0);
		//sb_log(LOG_INFO, "Update session for %d: num_rows = %d", desc->descriptor, num_rows);
		if (row != NULL) {
			if (atoi(row[0]) != desc->room_id) {
				sb_log(LOG_INFO, "Update session for %d: room changed to %d", desc->descriptor, desc->room_id);
				desc->room_id		= atoi(row[0]);
			}
			if (desc->room_id <= -1) {
				sb_log(LOG_INFO, "Update session for %d: %s logging out", desc->descriptor, desc->username);
				desc->conn_status = CON_QUITTING;
				add_to_output_queue(desc, "<br />[ LOGGING OUT ]<br />");

				sprintf(buffer, "DELETE FROM %ssessions WHERE session_id='%s'", db_pfx, (char *)desc->session_id);
				pthread_mutex_lock(&db_mutex);
				results			= db_query(dbconn, buffer);
				pthread_mutex_unlock(&db_mutex);
				if (results) db_clean_results(results);
			}
		} else {
			db_clean_results(results);
			add_to_output_queue(desc, "Session ID is no longer valid, please log back in.<br />");
			//add_to_output_queue(desc, (char *)desc->session_id);
			// session no longer valid, nix it
			desc->conn_status = CON_QUITTING;
			return -1;
		}
		db_clean_results(results);
	}

	return 0;
}

void update_all_sessions(void) {
	DESCRIPTOR_DATA	*tmp_desc;

	for (tmp_desc = descriptor_list; tmp_desc; tmp_desc = tmp_desc->next) {
		if (tmp_desc->session_id) {
			//if (mud_verbosity)
				//sb_log(LOG_INFO, "Updating session for descriptor %d", tmp_desc->descriptor);
			update_session(tmp_desc);
		}
	}
}

//
// This function used to pull events on a per-descriptor basis. Given that it did not scale well (more users
// caused more database traffic) most of the code was moved to master_poll_events.
//
// This function is still used for retrieving the initial "context" events that the user sees when they log
// in... so they have an idea what's currently being discussed when they enter the room.
//
int poll_events(DESCRIPTOR_DATA *desc) {
	char buffer[MAX_STRING_LENGTH];
        DB_RES  *results;
        DB_ROW  row;
	int	num_rows	= 0;
	int	new_last_event	= 0;
	int	i;

	sb_log(LOG_INFO, "poll_events: selecting initial events for %d", desc->descriptor);

	if (desc->last_event == 0) {
		sprintf(buffer,	"SELECT event_text,event_date,event_id,event_chat_flags FROM %sevents WHERE (room_id_to=%d OR room_id_to=-1 OR user_id_to=%d) "
				" ORDER BY event_id DESC LIMIT 20",
				db_pfx, desc->room_id, desc->user_id);
		pthread_mutex_lock(&db_mutex);
        	results		= db_query(dbconn, buffer);
		pthread_mutex_unlock(&db_mutex);
		num_rows	= db_num_rows(results);
		sb_log(LOG_INFO, "poll_events: %d results", num_rows);
        	if (results != NULL) {
			//add_to_output_queue(desc, "\r\n--StormeChat\r\nContent-Type: text/html\r\n\r\n");
			for (i = num_rows - 1; i >= 0; i--) {
				row	= db_fetch_row(results, i);
                        	sb_log(LOG_INFO, "poll_events: Retrieved event_id #%d successfully", atoi(row[2]));
                        	add_to_output_queue(desc, "<script>STC('");
				add_to_output_queue(desc, row[0]);
				add_to_output_queue(desc, "', ");
				add_to_output_queue(desc, row[3]);
				add_to_output_queue(desc, ");</script>\r\n"); //<br />\r\n");
				if (atoi(row[2]) > new_last_event) {
					new_last_event	= atoi(row[2]);
				}
                	}
                        db_clean_results(results);
        	} else {
			return -1;
		}
	}

	desc->last_event	= new_last_event;
	sb_log(LOG_INFO, "New last_event for %d is %d", desc->descriptor, desc->last_event);

	return 1;
}

//
// This function handles the auto-idling of users and timing out of inactive users.
//
// This does not do much handling of client-pull connections and focuses mainly on
// the server-push connections.
// 
void clean_old_sessions() {
	char		buffer[MAX_STRING_LENGTH];
        DB_ROW		row;
        DB_RES		*results;
        DB_RES		*results2;
	DESCRIPTOR_DATA	*tmp_desc;
	int		found_desc;
	int		idle_out;
	int		num_rows;
	int		i;

	//sb_log(LOG_INFO, "clean_old_sessions: starting cleanup...");
	
	//
	// Increment idle timer to allow for graceful timeouts of users.
	//
	sprintf(buffer, "UPDATE %ssessions SET idle_timer=idle_timer+1", db_pfx);
	pthread_mutex_lock(&db_mutex);
        db_query(dbconn, buffer);
	pthread_mutex_unlock(&db_mutex);

	//
	// Update the pull timer. Basically when someone queries events.php for client pull or SlimChat,
	// it resets this timer to 0. If the timer keeps incrementing, that indicates that they are not
	// actually pulling new events. Most common cause of this is the user closed the browser window
	// without logging out of chat.
	//
	// Bad users. No cookie.
	//
	sprintf(buffer, "UPDATE %ssessions SET pull_timer=pull_timer+1 WHERE server_push NOT IN (%d, %d)",
			db_pfx, MODE_SERVER_PUSH, MODE_SERVER_PUSH_PROXY);
	pthread_mutex_lock(&db_mutex);
        db_query(dbconn, buffer);
	pthread_mutex_unlock(&db_mutex);

	//
	// Clean up logged out sessions after they would see the message about being disconnected.
	//
	sprintf(buffer, "DELETE FROM %ssessions WHERE room_id < 0 AND idle_timer>%d", db_pfx, time_timeout);
	pthread_mutex_lock(&db_mutex);
       	db_query(dbconn, buffer);
	pthread_mutex_unlock(&db_mutex);

	//
	// Clean up idlers and closed client pull / slimchat sessions who are not moderators.
	//
	sprintf(buffer, "SELECT session_id,room_id,username,user_id,idle_timer,pull_timer,moderator,server_push "
			"FROM %ssessions WHERE server_push NOT IN (%d, %d)", db_pfx, MODE_SERVER_PUSH, MODE_SERVER_PUSH_PROXY);
	pthread_mutex_lock(&db_mutex);
       	results		= db_query(dbconn, buffer);
	pthread_mutex_unlock(&db_mutex);

	if (results != NULL) {
		num_rows	= db_num_rows(results);
		for (i = 0; i < num_rows; i++) {
			row	= db_fetch_row(results, i);

			if (atoi(row[5]) > 300 && (atoi(row[6]) != 1 || (atoi(row[6]) == 1 && atoi(row[7]) != MODE_SLIMCHAT))) {
				//
				// They have not pulled events recently -- browser window was probably closed. Boot them.
				//
				sprintf(buffer, "INSERT INTO %sevents (user_id_from, room_id_to, event_text, event_date, event_chat_flags) "
						"VALUES (%s, %s, '<i>*** %s has timed out.</i>', NOW(), 2)",
						db_pfx, row[3], row[1], row[2]);
				pthread_mutex_lock(&db_mutex);
       				db_query(dbconn, buffer);
				pthread_mutex_unlock(&db_mutex);

				sb_log(LOG_INFO, "clean_old_sessions: session ID '%s' has a pull timer of %s, timing them out.",
						row[0], row[5]);

				sprintf(buffer, "DELETE FROM %ssessions WHERE session_id='%s'",
						db_pfx, row[0]);
				pthread_mutex_lock(&db_mutex);
       				db_query(dbconn, buffer);
				pthread_mutex_unlock(&db_mutex);
			} else if (atoi(row[4]) > time_timeout) {
				sprintf(buffer, "INSERT INTO %sevents (user_id_from, room_id_to, event_text, event_date, event_chat_flags) "
			       			"VALUES	(%s, %s, '<i>*** %s has idled out.</i>', NOW(), 2)",
						db_pfx, row[3], row[1], row[2]);
				pthread_mutex_lock(&db_mutex);
       				db_query(dbconn, buffer);
				pthread_mutex_unlock(&db_mutex);

				sb_log(LOG_INFO, "clean_old_sessions: session ID '%s' has a timer of %s, idling them out.",
						row[0], row[4]);

				sprintf(buffer, "DELETE FROM %ssessions WHERE session_id='%s'",
						db_pfx, row[0]);
				pthread_mutex_lock(&db_mutex);
       				db_query(dbconn, buffer);
				pthread_mutex_unlock(&db_mutex);
			}
		}
		db_clean_results(results);
	}

	//
	// Clean up server push sessions. Idle and limbo warnings are sent as the server can track these warnings being sent
	// with ease, where client pull/slimchat sessions would have to store warning notifications in the database.
	//
	sprintf(buffer, "SELECT session_id,room_id,username,user_id,idle_timer "
			"FROM %ssessions WHERE server_push IN (%d, %d)", db_pfx, MODE_SERVER_PUSH, MODE_SERVER_PUSH_PROXY);
	pthread_mutex_lock(&db_mutex);
       	results		= db_query(dbconn, buffer);
	pthread_mutex_unlock(&db_mutex);

	if (results != NULL) {
		num_rows	= db_num_rows(results);
		for (i = 0; i < num_rows; i++) {
			row	= db_fetch_row(results, i);
			//sb_log(LOG_INFO, "Checking for descriptor to match session %s", row[0]);
			found_desc	= 0;
			idle_out	= 0;
			for (tmp_desc = descriptor_list; tmp_desc; tmp_desc = tmp_desc->next) {
				if ((char *)tmp_desc->session_id != NULL) {
					if (!strcasecmp(row[0], (char *)tmp_desc->session_id)) {
						found_desc++;
						if (atoi(row[4]) > time_timeout) {
							add_to_output_queue(tmp_desc, "<script>SendToChat('<b>[ TIMEOUT ]</b> -- Please click <a href=");
							add_to_output_queue(tmp_desc, site_url);
							add_to_output_queue(tmp_desc, " target=_top>here</a> to log back in.');</script>");
							tmp_desc->conn_status = CON_QUITTING;
							idle_out++;
						} else if (atoi(row[4]) > time_warn && tmp_desc->idle_warning == 2) {
							add_to_output_queue(tmp_desc, "<script>SendToChat('<b>+++ You will time out from inactivity in 10 minutes.</b>');</script>");
							tmp_desc->idle_warning++;
						} else if (atoi(row[4]) > time_limbo && tmp_desc->idle_warning == 1) {
							sprintf(buffer, "INSERT INTO %sevents (user_id_from, user_id_to, room_id_to, event_text, event_date) VALUES (%d, 0, %d, '<i>*** %s has gone into limbo.</i>', NOW())", db_pfx, tmp_desc->user_id, tmp_desc->room_id, tmp_desc->username);
							pthread_mutex_lock(&db_mutex);
							db_query(dbconn, buffer);
							pthread_mutex_unlock(&db_mutex);
							tmp_desc->idle_warning++;
						} else if (atoi(row[4]) > time_idle && tmp_desc->idle_warning == 0) {
							sprintf(buffer, "INSERT INTO %sevents (user_id_from, user_id_to, room_id_to, event_text, event_date) VALUES (%d, 0, %d, '<i>*** %s has become idle.</i>', NOW())", db_pfx, tmp_desc->user_id, tmp_desc->room_id, tmp_desc->username);
							pthread_mutex_lock(&db_mutex);
							db_query(dbconn, buffer);
							pthread_mutex_unlock(&db_mutex);
							tmp_desc->idle_warning++;
						} else if (atoi(row[4]) < 300) {
							tmp_desc->idle_warning = 0;
						}
					}
				}
			}

			//
			// have to allow time for people to log in after session creation...
			//
			if (!found_desc && (atoi(row[4]) > 30)) {
				sb_log(LOG_INFO, "clean_old_sessions: session ID '%s' has a timer of %s and no matching descriptor, timing them out.",
						row[0], row[4]);

				sprintf(buffer, "DELETE FROM %ssessions WHERE session_id='%s'", db_pfx, row[0]);
				pthread_mutex_lock(&db_mutex);
				results2	= db_query(dbconn, buffer);
				pthread_mutex_unlock(&db_mutex);
				if (results2) db_clean_results(results2);
	
				sprintf(buffer, "INSERT INTO %sevents (user_id_from, room_id_to, event_text, event_date, event_chat_flags) "
			       				"VALUES	(%s, %s, '*** %s has failed to connect, timing out.', NOW(), 2)", db_pfx, row[3], row[1], row[2]);
				pthread_mutex_lock(&db_mutex);
				results2	= db_query(dbconn, buffer);
				pthread_mutex_unlock(&db_mutex);
				if (results2) db_clean_results(results2);
			}

			//
			// They've not said anything in ages, disconnect them and let folks know they are gone.
			//
			if (idle_out) {
				sb_log(LOG_INFO, "clean_old_sessions: session ID '%s' has a timer of %s, idling them out.",
						row[0], row[4]);

				sprintf(buffer, "DELETE FROM %ssessions WHERE session_id='%s'", db_pfx, row[0]);
				pthread_mutex_lock(&db_mutex);
				results2	= db_query(dbconn, buffer);
				pthread_mutex_unlock(&db_mutex);
				if (results2) db_clean_results(results2);
	
				sprintf(buffer, "INSERT INTO %sevents (user_id_from, room_id_to, event_text, event_date, event_chat_flags) "
			       				"VALUES	(%s, %s, '<i>*** %s has idled out.</i>', NOW(), 2)", db_pfx, row[3], row[1], row[2]);
				pthread_mutex_lock(&db_mutex);
				results2	= db_query(dbconn, buffer);
				pthread_mutex_unlock(&db_mutex);
				if (results2) db_clean_results(results2);
			}

		}
		db_clean_results(results);
	}
	//sb_log(LOG_INFO, "clean_old_sessions: ending cleanup...");
}

// 
// Inform chat that someone has been disconnected and try to track the particular error
// as best as possible.
//
void error_event_add(DESCRIPTOR_DATA *desc, char *err_msg) {
	char		buffer[MAX_STRING_LENGTH];
        DB_RES		*results;
        DB_ROW		row;

	if ((char *)desc->session_id == NULL)
		return;

	desc->conn_status	= CON_QUIT;

	sprintf(buffer, "SELECT session_id,room_id,username,user_id FROM %ssessions WHERE session_id='%s'",
			db_pfx, (char *)desc->session_id);

	pthread_mutex_lock(&db_mutex);
        results		= db_query(dbconn, buffer);
	pthread_mutex_unlock(&db_mutex);
	if (results != NULL) {
		if ((row = db_fetch_row(results, 0)) != NULL) {
			sprintf(buffer, "INSERT INTO %sevents (user_id_from, room_id_to, event_text, event_date, event_chat_flags) "
			       		"VALUES	(%s, %s, '*** %s has been disconnected: %s.', NOW(), 2)", db_pfx, row[3], row[1], row[2], err_msg);
			pthread_mutex_lock(&db_mutex);
			db_query(dbconn, buffer);
			pthread_mutex_unlock(&db_mutex);
		}
		db_clean_results(results);
	}

	sprintf(buffer, "DELETE FROM %ssessions WHERE session_id='%s'",
			db_pfx, (char *)desc->session_id);

	pthread_mutex_lock(&db_mutex);
        db_query(dbconn, buffer);
	pthread_mutex_unlock(&db_mutex);
}

//
// This function checks for time based alarms and moves them to the event queue when the time
// expires. Handy for word wars, reminders, and time-based shutdowns.
//
void master_poll_alarms(void) {
	char		buffer[MAX_STRING_LENGTH];
        DB_RES		*results;
        DB_ROW		row;
	int		num_rows	= 0;
	int		i, etype, flags;

	//sb_log(LOG_INFO, "master_poll_alarms: Checking for alarms...");

	sprintf(buffer, "SELECT alarm_id,alarm_user_id,alarm_room_to,alarm_user_to,alarm_text,alarm_type FROM "
			"%salarms WHERE alarm_time < NOW() AND alarm_inactive=0", db_pfx);
	pthread_mutex_lock(&db_mutex);
        results		= db_query(dbconn, buffer);
	pthread_mutex_unlock(&db_mutex);
	num_rows	= db_num_rows(results);
	if (num_rows > 0) 
		sb_log(LOG_INFO, "Retrieving alarm events: %d results", num_rows);

	for (i = 0; i < num_rows; i++) {
		row	= db_fetch_row(results, i);
		etype	= atoi(row[5]);
		flags	= 0;
		if (etype == 1) {
			flags	= 4;
		}
		snprintf(buffer, MAX_STRING_LENGTH,
				"INSERT INTO %sevents (user_id_from, room_id_to, user_id_to, event_text, event_date, event_chat_flags) "
				"VALUES (%s, %s, %s, '%s', NOW(), %d)",
				db_pfx, row[1], row[2], row[3], row[4], flags);
		pthread_mutex_lock(&db_mutex);
        	db_query(dbconn, buffer);
		pthread_mutex_unlock(&db_mutex);

		sprintf(buffer, "UPDATE %salarms SET alarm_inactive=1 WHERE alarm_id=%s", db_pfx, row[0]);
		pthread_mutex_lock(&db_mutex);
        	db_query(dbconn, buffer);
		pthread_mutex_unlock(&db_mutex);
	}
	db_clean_results(results);
}

void bootstrap_db_lastevent(void) {
	char		buffer[MAX_STRING_LENGTH];
        DB_RES		*results;
        DB_ROW		row;

	//
	// Find the last event sent to chat...
	//
	sprintf(buffer,	"SELECT event_id FROM %sevents ORDER BY event_id DESC LIMIT 1", db_pfx);
	pthread_mutex_lock(&db_mutex);
       	results		= db_query(dbconn, buffer);
	pthread_mutex_unlock(&db_mutex);
	if (results != NULL) {
		row		= db_fetch_row(results, 0);
		last_event	= atoi(row[0]);
		sb_log(LOG_INFO, "Retrieving last event: %d", atoi(row[0]));
	} else {
		sb_log(LOG_CRIT, "Could not retrieve last event");
		exit(-1);
	}
}

//
// This is the workhorse of the event code. This function goes through and retrieves all the events
// which are more recent than the last time that it attempted to poll. Then it loops through the list
// of current connections and based on which room they are in and if it was a private message, adds
// the event to their output queue if appropriate.
//
int master_poll_events(void) {
	char		buffer[MAX_STRING_LENGTH];
	DESCRIPTOR_DATA	*tmp_desc;
        DB_RES		*results;
        DB_ROW		row;
	int		num_rows	= 0;
	int		event_id	= 0;
	int		room_id		= 0;
	int		user_id		= 0;
	int		i;

	//sb_log(LOG_INFO, "Polling master events");

	sprintf(buffer, "SELECT event_text,event_date,event_id,room_id_to,user_id_to,event_chat_flags FROM %sevents WHERE event_id>%d ORDER BY event_id", db_pfx, last_event);
	pthread_mutex_lock(&db_mutex);
       	results		= db_query(dbconn, buffer);
	pthread_mutex_unlock(&db_mutex);
	num_rows	= db_num_rows(results);
	if (num_rows)
		sb_log(LOG_INFO, "Retrieving NEW events SINCE %d: %d results", last_event, num_rows);

	if (results != NULL) {
		//while ((row = db_fetch_row(results)) != NULL) {
		for (i = 0; i < num_rows; i++) {
			row	= db_fetch_row(results, i);
			event_id	= atoi(row[2]);
			room_id		= atoi(row[3]);
			user_id		= atoi(row[4]);

                	//sb_log(LOG_INFO, "Retrieved event_id #%d successfully", event_id);

			for (tmp_desc = descriptor_list; tmp_desc; tmp_desc = tmp_desc->next) {
				if ((char *)tmp_desc->session_id == NULL)
					continue;

				if (tmp_desc->conn_status == CON_GET_SID || tmp_desc->conn_status == CON_QUIT)
					continue;

				if ((char *)tmp_desc->username == NULL) {
					sb_log(LOG_INFO, "NULL USERNAME FOR DESCRIPTOR %d", tmp_desc->descriptor);
					continue;
				}

				if (tmp_desc->room_id == room_id || room_id == -1 || tmp_desc->user_id == user_id) {
					sb_log(LOG_INFO, "Sending event id #%d to descriptor %d (%s) [%s]", event_id, tmp_desc->descriptor, tmp_desc->username, tmp_desc->host);
                        		add_to_output_queue(tmp_desc, "<script>STC('");
					add_to_output_queue(tmp_desc, row[0]);
					add_to_output_queue(tmp_desc, "', ");
					add_to_output_queue(tmp_desc, row[5]);
					add_to_output_queue(tmp_desc, ");</script>\r\n"); //<br />\r\n");
					if (event_id > tmp_desc->last_event) {
						tmp_desc->last_event	= event_id;
					}
				}
			}

			if (event_id > last_event) {
				last_event	= event_id;
			}
			
        	}
		//add_to_output_queue(desc, "<script language=javascript>\r\nwindow.scrollByPages(12);\r\n</script>\r\n");
                db_clean_results(results);
	} else {
		return -1;
	}

	//sb_log(LOG_INFO, "New last_event is %d", last_event);

	return 1;
}

